package Transformers;

import Types.Complex;

/**
 * Represents the fourier transform.
 * @author Cliff Laschet
 */
public class Fourier {

    /**
     * 
     * @param source
     * @return 
     */
    public double[] forward(double[] source) {
        int N = source.length;
        double[] result = new double[N];
        double[] a = new double[N];
        double[] b = new double[N];

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                a[i] += source[j] * Math.cos(i * j * 2 * Math.PI / N);
                b[i] -= source[j] * Math.sin(i * j * 2 * Math.PI / N);
            }
            a[i] /= N;
            b[i] /= N;

            result[i] = Math.sqrt(a[i] * a[i] + b[i] * b[i]);
        }

        return result;
    }

    /**
     * 
     * @param arrFreq
     * @return 
     */
    public double[] reverse(double[] transformed) {
        int N = transformed.length;
        double[] original = new double[N];
        double[] a = new double[N];
        double[] b = new double[N];

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                double cos = Math.cos(j * (2. * Math.PI * i / N));
                double sin = Math.sin(j * (2. * Math.PI * i / N));
                a[i] += transformed[j] * cos - transformed[j] * sin;
                b[i] += transformed[j] * sin + transformed[j] * cos;
            }
            original[i] = Math.sqrt(a[i] * a[i] + b[i] * b[i]);
        }
        return original;
    }

    public double[] reverse2(double[] arrFreq) {

        int m = arrFreq.length;
        double[] arrTime = new double[m]; // result

        int n = m >> 1; // half of m

        for (int i = 0; i < n; i++) {

            int iR = i * 2;
            int iC = i * 2 + 1;

            arrTime[iR] = 0.;
            arrTime[iC] = 0.;

            double arg = 2. * Math.PI * (double) i / (double) n;

            for (int k = 0; k < n; k++) {

                int kR = k * 2;
                int kC = k * 2 + 1;

                double cos = Math.cos(k * arg);
                double sin = Math.sin(k * arg);

                arrTime[iR] += arrFreq[kR] * cos - arrFreq[kC] * sin;
                arrTime[iC] += arrFreq[kR] * sin + arrFreq[kC] * cos;

            } // k

        } // i

        return arrTime;
    } // reverse

    /**
     * 
     * @param source
     * @return 
     */
    public Complex[] complexForward(Complex[] source) {
        Complex[] integrated = new Complex[source.length];
        for (int i = 0; i < integrated.length; i++) {
            integrated[i] = new Complex(0, 0);
        }

        for (int u = 0; u < source.length; u++) {
            for (int k = 0; k < source.length; k++) {
                double p = -2 * Math.PI * u * k / source.length;
                Complex number = integrated[u];
                number.addReal(source[k].getRealPart() * Math.cos(p)
                        - source[k].getImagPart() * Math.sin(p));
                number.addImaginary(source[k].getImagPart() * Math.cos(p)
                        + source[k].getRealPart() * Math.sin(p));
            }
            Complex number = integrated[u];
            number.setRealPart(number.getRealPart() / source.length);
            number.setImagPart(number.getImagPart() / source.length);
        }

        return integrated;
    }

    /**
     * 
     * @param source
     * @return 
     */
    public Complex[] complexReverse(Complex[] source) {
        Complex[] original = new Complex[source.length];
        for (int i = 0; i < original.length; i++) {
            original[i] = new Complex(0, 0);
        }

        for (int u = 0; u < source.length; u++) {
            for (int k = 0; k < source.length; k++) {
                double p = 2 * Math.PI * u * k / source.length;
                Complex number = original[u];
                number.addReal(source[k].getRealPart() * Math.cos(p)
                        - source[k].getImagPart() * Math.sin(p));
                number.addImaginary(source[k].getImagPart() * Math.cos(p)
                        + source[k].getRealPart() * Math.sin(p));
            }
            Complex number = original[u];
            number.setRealPart(number.getRealPart() / source.length);
            number.setImagPart(number.getImagPart() / source.length);
        }

        return original;
    }
}
